package seedpod.model;

/**
 * Copyright 2009 University of Washington This code is released under the GPL version 3 - with a copy of the license
 * included
 * 
 * @author Maya Hao Li
 */
/**
 * <p>
 * Title: KnowldgeBase to Database
 * </p>
 * <p>
 * Description: Associate a slot with a Cls for which it is a template-slot in. In particular, this slot may be a
 * RDB_attribute instance slot
 * </p>
 * <p>
 * Copyright: Copyright (c) 2002
 * </p>
 * <p>
 * Company: University of Washington BHI
 * </p>
 * 
 * @author Hao Li
 * @version 2.0
 */

import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.KnowledgeBase;
import edu.stanford.smi.protege.model.Slot;
import edu.stanford.smi.protege.model.ValueType;

import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import seedpod.model.rdb.RdbValueType;
import seedpod.util.XMLWriter;

/**
 * A RdbSlot has a slot and a specific domain class.
 * 
 * @author haoli
 */
public class RdbSlot {
	public static final int DEFAULT_VARCHAR_LENGTH = 50;
	protected static final RdbValueType DEFAULT_DATABASE_TYPE = RdbValueType.INTEGER;
	protected static final ValueType DEFAULT_VALUE_TYPE = ValueType.INTEGER;
	protected static final boolean DEFAULT_IS_UNIQUE = false;
	protected static final boolean DEFAULT_IS_INDEXED = false;

	private Slot _slot;
	private Cls _domainCls;
	private Cls _slotMetaCls;
	private KnowledgeBase _kb; // pointer to containing kb
	private SeedpodSystemFrames _spSystemFrames;
	private boolean _isRdbSlot = true;
	private boolean _assocToDomainCls = true;
	private ArrayList<String> _errorMessage = new ArrayList<String>();
	private boolean _isReified = true; // reified slots get maps
	private RdbSlot _inverse;

	public RdbSlot(Slot slot, Cls cls, KnowledgeBase kb) {
		_slot = slot;
		_domainCls = cls;
		_kb = kb;
		_spSystemFrames = new SeedpodSystemFrames( _kb );

		_slotMetaCls = _slot.getDirectType();
		_isRdbSlot = _slot.hasType( _kb.getCls( SeedpodModel.DEFAULT_META_RDB_SLOT ) );
	}

	public RdbSlot(Slot slot, Cls cls, KnowledgeBase kb, boolean isRdbAttribute) {
		_slot = slot;
		_domainCls = cls;
		_kb = kb;
		_isRdbSlot = isRdbAttribute;
	}

	public Slot getSlot() {
		return _slot;
	}

	public Cls getSlotMetaCls() {
		return _slotMetaCls;
	}

	public Cls getDomainCls() {
		return _domainCls;
	}

	public String toString() {
		return "(Slot)[" + _domainCls.toString() + ":" + _slot.toString() + "]";
		/*
		 * return "SLOT::" + _relationSlot.getName() + "| " + (_isReified ? _domainCls.getName() : " NOT reified") + ("| " +
		 * _domainCls.getTemplateSlotValueType( _relationSlot )) + ("| " + _domainCls.getTemplateSlotMinimumCardinality( _relationSlot
		 * )) + ":" + (_domainCls.getTemplateSlotAllowsMultipleValues( _relationSlot ) ? "*" : "1") + "</slot>";
		 */
	}

	public void serialize( PrintStream ps ) throws seedpod.util.XMLWriter.XMLException, IOException {
		XMLWriter out = new XMLWriter();
		toXML( out ).serialize( ps );
	}

	public XMLWriter toXML( XMLWriter out ) throws seedpod.util.XMLWriter.XMLException {
		XMLWriter slotOut = out.addElement( "slot" );
		slotOut.addElement( "name", getName() );
		slotOut.addElement( "_isReified", Boolean.toString( _isReified ) );
		slotOut.addElement( "type", getValueType().toString() );
		slotOut.addElement( "minimumCardinality", Integer.toString( getMinimumCardinality() ) );
		slotOut.addElement( "maximumCardinality", Integer.toString( getMaximumCardinality() ) );
		return slotOut;
	}

	public void setDefaultValueType() {
		_slot.setValueType( DEFAULT_VALUE_TYPE );
	}

	public boolean isUnique() {
		Slot uniqueOwnSlot = _kb.getSlot( SeedpodModel.RdbSlot.UNIQUE );
		if ( uniqueOwnSlot != null ) {
			Boolean u = (Boolean)_slot.getDirectOwnSlotValue( uniqueOwnSlot );
			return (u == null) ? DEFAULT_IS_UNIQUE : u.booleanValue();
		} else
			return DEFAULT_IS_UNIQUE;
	}

	public boolean equals( Object slot ) {
		RdbSlot otherSlot = (RdbSlot)slot;
		return (_slot.equals( otherSlot.getSlot() ) && _domainCls.equals( otherSlot.getDomainCls() ));
	}

	public int hashCode() {
		int hash = 1;
		hash = hash * 31 + getName().hashCode();
		hash = hash * 31 + getDomainCls().getName().hashCode();
		return hash;
	}

	public String getName() {
		return _slot.getName();
	}

	public boolean isRdbSlot() {
		return _isRdbSlot;
	}

	public static boolean isRdbSlot( Slot slot ) {
		KnowledgeBase kb = slot.getKnowledgeBase();
		return (slot.hasType( kb.getCls( SeedpodModel.DEFAULT_META_RDB_SLOT ) ));
	}

	public ValueType getValueType() {
		return _slot.getValueType();
	}

	public void setInverse( RdbSlot inv ) {
		_inverse = inv;
	}

	public RdbSlot getInverse() {
		return _inverse;
	}

	public boolean hasAssociatedCls() {
		return _assocToDomainCls;
	}

	public void setIsAssociated( boolean show ) {
		_assocToDomainCls = show;
	}

	public void setIsAssociated( boolean assoc, String error ) {
		_assocToDomainCls = assoc;
		_errorMessage.add( error );
	}

	public boolean isAssociated() {
		return _assocToDomainCls;
	}

	public boolean isAtomicType() {
		ValueType stype = _slot.getValueType();
		return (stype.equals( ValueType.BOOLEAN ) || stype.equals( ValueType.FLOAT )
				|| stype.equals( ValueType.INTEGER ) || stype.equals( ValueType.STRING ) || stype
				.equals( ValueType.SYMBOL ));
	}

	public static boolean isAtomicType( Slot slot ) {
		ValueType stype = slot.getValueType();
		return (stype.equals( ValueType.BOOLEAN ) || stype.equals( ValueType.FLOAT )
				|| stype.equals( ValueType.INTEGER ) || stype.equals( ValueType.STRING ) || stype
				.equals( ValueType.SYMBOL ));
	}

	@SuppressWarnings("unchecked")
	public Collection getAllowedClses() {
		return _slot.getAllowedClses();
	}

	/**
	 * since the transformation only deals with one allowed class in the instance value, might as well just return that
	 * one class for now.
	 * 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public Cls getAllowedCls() {
		Iterator clsItr = _slot.getAllowedClses().iterator();
		if ( clsItr.hasNext() )
			return (Cls)clsItr.next();
		else
			return null;
	}

	public int getMaximumCardinality() {
		return _domainCls.getTemplateSlotMaximumCardinality( _slot );
	}

	public int getMinimumCardinality() {
		return _slot.getMinimumCardinality();
	}

	public Number getMinimumValue() {
		return _slot.getMinimumValue();
	}

	public Number getMaximumValue() {
		return _slot.getMaximumValue();
	}

	public boolean allowsMultipleValues() {
		return _domainCls.getTemplateSlotAllowsMultipleValues( _slot );
	}

	public Slot getInverseSlot() {
		return _slot.getInverseSlot();
	}

	public boolean isRequired() {
		return (_slot.getMinimumCardinality() > 0);
	}

	public RdbValueType getDatabaseType() {
		RdbValueType dbType;
		Slot databaseSlot = _spSystemFrames.getDatabaseTypeSlot();
		String dbTypeName = (String)_slot.getDirectOwnSlotValue( databaseSlot );
		dbType = (dbTypeName == null) ? RdbValueType.VARCHAR : RdbValueType.valueOf( dbTypeName );
		return dbType;
	}

	public String getDatabaseTypeParameter() {
		String dbTypeParam = (String)_slot.getDirectOwnSlotValue( _spSystemFrames.getDatabaseTypeParameterSlot() );
		if ( dbTypeParam != null )
			return dbTypeParam;
		else if ( getDatabaseType().equals( RdbValueType.VARCHAR ) || getDatabaseType().equals( RdbValueType.CHARACTER ) ) {
			setDatabaseTypeParameter( String.valueOf( DEFAULT_VARCHAR_LENGTH ) );
			return String.valueOf( DEFAULT_VARCHAR_LENGTH );
		} else
			return "";
	}

	public String getDatabaseTypeString() {
		return getDatabaseType().toString() + PARAN( getDatabaseTypeParameter() );
	}

	public String getFormWidget() {
		Slot formWidgetSlot = _spSystemFrames.getFormWidgetSlot();
		String widget = (String)_slot.getDirectOwnSlotValue( formWidgetSlot );
		String defaultWidget = (String)getFirst( formWidgetSlot.getDefaultValues() );
		return (widget == null) ? defaultWidget : widget;
	}

	public String getFormWidgetParameter() {
		Slot formWidgetParamSlot = _spSystemFrames.getFormWidgetParameterSlot();
		String widgetParam = (String)_slot.getDirectOwnSlotValue( formWidgetParamSlot );
		String defaultParam = "";
		return (widgetParam == null) ? defaultParam : widgetParam;
	}

	public String getViewWidget() {
		Slot viewWidgetSlot = _spSystemFrames.getViewWidgetSlot();
		String widget = (String)_slot.getDirectOwnSlotValue( viewWidgetSlot );
		return (widget == null) ? "" : widget;
	}

	public String getUnit() {
		Slot unitSlot = _spSystemFrames.getUnitSlot();
		String unit = (String)_slot.getDirectOwnSlotValue( unitSlot );
		String defaultUnit = "";
		return (unit == null) ? defaultUnit : unit;
	}

	public String getViewWidgetParameter() {
		Slot viewWidgetParamSlot = _spSystemFrames.getViewWidgetParameterSlot();
		String widgetParam = (String)_slot.getDirectOwnSlotValue( viewWidgetParamSlot );
		String defaultParam = "";
		return (widgetParam == null) ? defaultParam : widgetParam;
	}

	public boolean isDatabaseIndex() {
		Slot isDatabaseSlot = _spSystemFrames.getIsDatabaseIndexSlot();
		Boolean defaultVal = (Boolean)getFirst( isDatabaseSlot.getDefaultValues() );
		Boolean isDatabaseIndexed = (Boolean)_slot.getDirectOwnSlotValue( isDatabaseSlot );
		return (isDatabaseIndexed == null) ? defaultVal.booleanValue() : isDatabaseIndexed.booleanValue();
	}

	@SuppressWarnings("unchecked")
	private static Object getFirst( Collection collection ) {
		Iterator iter = collection.iterator();
		if ( iter.hasNext() )
			return iter.next();
		else
			return null;
	}

	public String getDefaultValue() {
		Object val = getFirst( _slot.getDefaultValues() );
		return (val == null) ? "" : val.toString();
	}

	@SuppressWarnings("unchecked")
	public Collection getAllowedValues() {
		return _slot.getAllowedValues();
	}

	/**
	 * If the slot is of Instance type, check if the referenced class that is an in-lined value type, i.e. do not create
	 * reference for the cls
	 * 
	 * @return
	 */
	public boolean referencesInlineCls() {
		boolean referenceInline = false;
		if ( _slot.getValueType().equals( ValueType.INSTANCE ) ) {
			Cls allowedCls = getAllowedCls();
			if ( allowedCls != null ) {
				RdbCls referencedCls = new RdbCls( allowedCls, _kb );
				if ( referencedCls.isInline() )
					referenceInline = true;
			}
		}
		return referenceInline;
	}

	/*
	 * public boolean isInline() { Slot inLineSlot = _spSystemFrames.getIsInlineSlot(); Boolean defaultVal =
	 * (Boolean)getFirst( inLineSlot.getDefaultValues() ); Boolean isInline = (Boolean)_relationSlot.getDirectOwnSlotValue(
	 * inLineSlot ); return (isInline == null) ? defaultVal.booleanValue() : isInline.booleanValue(); }
	 */

	private static String PARAN( String s ) {
		if ( s.length() > 0 )
			return "(" + s + ")";
		else
			return "";
	}

	public void setFormParameter( String formParam ) {
		Slot formWidgetParamSlot = _spSystemFrames.getFormWidgetParameterSlot();
		_slot.setDirectOwnSlotValue( formWidgetParamSlot, formParam );
	}

	public void setViewParameter( String viewParam ) {
		Slot viewWidgetParamSlot = _spSystemFrames.getFormWidgetParameterSlot();
		_slot.setDirectOwnSlotValue( viewWidgetParamSlot, viewParam );
	}

	public void setDatabaseTypeParameter( String dbParam ) {
		Slot dbParamSlot = _spSystemFrames.getDatabaseTypeParameterSlot();
		_slot.setDirectOwnSlotValue( dbParamSlot, dbParam );
	}

	public void setDatabaseType( RdbValueType dbtype ) {
		Slot databaseType = _spSystemFrames.getDatabaseTypeSlot();
		_slot.setDirectOwnSlotValue( databaseType, dbtype.toString() );
	}

	public void setFormWidget( String widget ) {
		Slot widgetSlot = _spSystemFrames.getFormWidgetSlot();
		_slot.setDirectOwnSlotValue( widgetSlot, widget );
	}

	public void setViewWidget( String widget ) {
		Slot viewSlot = _spSystemFrames.getViewWidgetSlot();
		_slot.setDirectOwnSlotValue( viewSlot, widget );
	}

	public void setExpression( String expression ) {
		Slot expressionSlot = _spSystemFrames.getValueExpressionSlot();
		_slot.setDirectOwnSlotValue( expressionSlot, expression );
	}

	public String getExpression() {
		Slot expressionSlot = _spSystemFrames.getValueExpressionSlot();
		String expression = (String)_slot.getDirectOwnSlotValue( expressionSlot );
		return (expression == null) ? "" : expression;
	}

	public Float getViewSequence() {
		Slot sequenceSlot = _spSystemFrames.getViewSequenceSlot();
		Float seq = (Float)_slot.getDirectOwnSlotValue( sequenceSlot );
		return (seq == null) ? new Float( 0.0 ) : seq;
	}

	public String getUserDefinedName() {
		Slot userNameSlot = _spSystemFrames.getUserAssignedNameSlot();
		String userName = (String)_slot.getDirectOwnSlotValue( userNameSlot );
		return (userName == null) ? "" : userName;
	}

	public void setUserDefinedName( String name ) {
		Slot userNameSlot = _spSystemFrames.getUserAssignedNameSlot();
		_slot.setDirectOwnSlotValue( userNameSlot, name );
	}

	public boolean isIndexed() {
		boolean isIndexed = DEFAULT_IS_INDEXED;
		Slot indexSlot = _spSystemFrames.getDatabaseIndexSlot();
		Boolean u = (Boolean)_slot.getDirectOwnSlotValue( indexSlot );
		isIndexed = ((u == null) ? isIndexed : u.booleanValue());
		return isIndexed;
	}

	/**
	 * check whether the instance type slot is referencing a datatype cls
	 * 
	 * @return
	 */
	public boolean referenceDataTypeCls() {
		boolean refDataType = false;
		if ( _slot.getValueType().equals( ValueType.INSTANCE ) ) {
			Cls allowedCls = getAllowedCls();
			if ( allowedCls != null ) {
				RdbCls refCls = new RdbCls( allowedCls, _kb );
				Cls dataTypeCls = _kb.getCls( SeedpodModel.RdbCls.DATA_TYPE );
				if ( _kb.hasSuperclass( refCls.getCls(), dataTypeCls ) )
					refDataType = true;
			}
		}
		return refDataType;
	}

}